/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package TablaSimbolos;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 *
 * @author Diego
 */
public class entradaClase extends entradaTS {

    boolean estaDeclarada; //indica si esta el classDef (declarada)
    boolean estaDefinida; //indica si esta el class (definida)
    boolean estipoprimitivo; //si es de tipo int, char, boolean o String.
    boolean constructordefault;//indica si tiene el constructor por defecto.
    boolean tieneconstructor; //indica si la clase tiene definido el constructor
    entradaClase padre; //apunta a su ancestro en caso de existir uno.
    String mensajeError;
    String msgErrorClase;
    LinkedHashMap<String, entradaMetodo> tablaMetodo = new LinkedHashMap();
    LinkedHashMap<String, entradaVariable> tablaVariable = new LinkedHashMap();
    private int mayorOffset = 0;
    private String VT;
    int lineaerror;
    
    public void setLineaError(int nroLinError){
    lineaerror = nroLinError;
    }
    
    public int getLineaError(){
    return lineaerror;
    }

    public String getVT() {
        return VT;
    }

    public void setVT(String VT) {
        this.VT = VT;
    }

    public int getMayorOffset() {
        return mayorOffset;
    }

    public void setMayorOffset(int mayorOffset) {
        this.mayorOffset = mayorOffset;
    }
    
    
    
    public void setConstructordefault(boolean constructordefault) {
        this.constructordefault = constructordefault;
    }

    public LinkedHashMap<String, entradaMetodo> getTablaMetodo() {
        return tablaMetodo;
    }

    public LinkedHashMap<String, entradaVariable> getTablaVariable() {
        return tablaVariable;
    }

    /**
     * Setea el atributo tiene constructor que indica si la clase ha defindo su 
     * constructor
     */
    public void setearConstructor() {
        tieneconstructor = true;
    }

    /**
     * Consulta si la clase tiene definido su constructor
     * @return boolean Verdadero en caso de que se haya definido el constructor 
     * de la clase, falso en caso contrario.
     */
    public boolean tieneDefinidoConstructor() {
        return tieneconstructor;
    }

    /**
     * Cuando el atributo estipoprimitivo indicando que la clase actual
     * representa uno de los tipos primitivos
     */
    public void setearPrimitivo() {
        estipoprimitivo = true;
    }

    /**
     * Devuelve true en caso de que la clase sea de tipo primitivo
     * @return boolean true si la clase es de tipo primitivo (int,char,boolean,String)
     */
    public boolean esTipoPrimitivo() {
        return estipoprimitivo;
    }

    /**
     * Retorna si la clase representa a la palabra reservada void
     * @return boolean indica si es del tipo de metodo void.
     */
    public boolean esVoid() {
        return this.darNombre().compareTo("void") == 0;
    }

    /**
     * Cuando se encuentra el classDef se setea a verdadero (declaracion)
     */
    public void setearDeclarada() {
        estaDeclarada = true;
    }

    /**
     * Consulta si el classDef ha sido declarado (declaracion)
     * @return boolean true si la clase fue declarada, es decir existe un classDef     * 
     */
    public boolean estaDeclarada() {
        return estaDeclarada;
    }

    /**
     * Setea como clase Definida cuando se encuentra el correspondiente class.
     */
    public void setearDefinida() {
        estaDefinida = true;
    }

    /**
     * Consulta si esta definido el class (definicion)
     * @return boolean Verdadero en caso que la clase este definida es decir 
     * que existe el correspondiente class. Falso en caso contrario.
     */
    public boolean estaDefinida() {
        return estaDefinida;
    }

    
    /**
     * Retorna el tamanio del CIR. 
     * @return Devuelve el tamaÃ±o del CIR (class instance record)
     */
    public int darTamanio() {
        return this.tablaVariable.size();
    }

    /**
     * setPadre: setea el atrubuto padre indicando de quien hereda. 
     * Esto es cuando aparece un extends.
     * @param ancestro apunta al padre de la clase.
     * Controla que no haya herencia circular a causa de la nueva asignacion.
     * En caso de ocurrir un error arroja una excepcion informando la causa 
     * que produjo el error. 
     * @throws Exception si encuentra herencia circular al asignar el padre y 
     * completar el circulo.
     */
    public void setPadre(entradaClase ancestro) throws Exception {
        if (ancestro.estaDeclarada()) {
            padre = ancestro;
        } else {
            String m = "Se intenta asignar como padre de " + this.darNombre() + " a una clase no declarada " + ancestro.darNombre();
            msgErrorClase = errorTS.construirErrorTS(m,lineaerror);
            throw new errorTS(msgErrorClase);
        }
    }

    /**
     * Indica si la clase pasada como parametro es ancestro de la actual.
     * Posible uso para ver tipos compatibles.
     * @param posiblePadre indica si es el posible ancestro de la clase actual.    
     * @return boolean Devuelve true en caso que el parametro posiblePadre sea
     * el ancestro de la clase actual.
     */
    public boolean esAncestro(entradaClase posiblePadre) {
        boolean eselancestro = false;
        boolean seguir = true;
        entradaClase aux = this.darPadre();
        while ((seguir) && (aux != null)) {
            if (aux.darNombre().compareTo(posiblePadre.darNombre()) == 0) {
                seguir = false;
                eselancestro = true;
            }
            aux = aux.darPadre();
        }
        return eselancestro;
    }

    /**
     * Dado una entrada de clase retorna su padre si existe.
     * @return entradaClase devuelve el padre de la clase actual -null en caso
     * de que no tenga padre (no hereda de ninguna clase).
     */
    public entradaClase darPadre() {
        return padre;
    }

    /***********************************************************************/
    /* Implementacion de metodos para el manejo de tabla metodos/variables
    /***********************************************************************/
    /************************************************************************/
    /*Metodos:(Referido al manejo de la tabla de metodos)*/
    /************************************************************************/
    /**
     * Indica se el nombre del metodo (nameMethod) pertenece a la tabla de Metodo.
     * @param nameMethod es el nombre del metodo a consultar para ver si se 
     * encuentra en la tabla de metodos.
     * @return boolean true si el metodo se encuentra en la tabla de metodo. Falso 
     * caso contrario.
     */
    public boolean perteneceTM(String nameMethod) {
        if (this.tablaMetodo.get(nameMethod) != null) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Dado el nombre de una metodo retorna la entrada a esa metodo.
     * @param key dada una llave de tipo String busca en la tabla de Metodos
     * si dicha clave esta presente.
     * @return entradaMetodo si la llave es encontrada devuelve una entrada a ese 
     * metodo con nombre key.
     */
    public entradaMetodo darMetodo(String key) throws Exception {
        if (perteneceTM(key)) {
            return this.tablaMetodo.get(key);
        } else {
            return null;
            //mensajeError = "Esta intentando buscar una metodo que no existe:" + key;            
            //throw new errorTS(mensajeError);
        }
    }

    public boolean isConstructordefault() {
        return constructordefault;
    }

    /**
     * Metodo privado para comparar el nombre de una entradaClase con un nombre
     * pasado.
     * @param em es la entradaMetodo cuyo nombre se quiere comparar
     * @param nombreacomparar es el nombre que se quiere comparar con entradaMetodo.
     * @return Devuelve verdadero si el nombre de la entrada coincide con el nombre
     * a comparar.
     */
    private boolean nombresIguales(entradaMetodo em, String nombreacomparar) {
        return em.darNombre().compareTo(nombreacomparar) == 0;
    }

    /**
     * insertarMetodoTM agrega una entrada de metodo a la tabla de metodos.
     * Este metodo es usado en la parte de declaracion (classDef)para ser 
     * consistente luego con la parte de definicion.
     * @param entryMethod es la entrada de metodo a ser agregada en la tabla de
     * metodos.
     * @throws errorTS  arroja un error si el metodo que se intenta agragar ya
     * esta en la tabla de metodos. 
     */
    public void insertarMetodoTM(entradaMetodo entryMethod) throws Exception {
        String m;
        boolean tienenombredelaclase, noesconstructor;
        int offset;
        if (!this.perteneceTM(entryMethod.darNombre())) {
            //pregunto si estoy insertando el constructor o un metodo
            //ya que hay que controlar que un metodo no tenga el nombre de
            //la clase.
            //tienenombredelaclase= entryMethod.darNombre().compareTo(this.darNombre())==0;
            tienenombredelaclase = nombresIguales(entryMethod, this.darNombre());
            noesconstructor = entryMethod.esConstructorDeClase();
            if ((tienenombredelaclase) && !(noesconstructor)) {
                m = "Intenta declarar un metodo " + entryMethod.darNombre() + " con el mismo nombre de la clase. ";
                mensajeError = errorTS.construirErrorTS(m, 1);
                throw new errorTS(mensajeError);
            } else {
                entryMethod.setearEtiqueta("l"+ this.nombre + entryMethod.darNombre());
                this.tablaMetodo.put(entryMethod.darNombre(), entryMethod);
            }
        } else {
            //es propio
            if (this.darMetodo(entryMethod.darNombre()).darNombreClasePertenece().equals(nombre)) {
                m = "Metodo con nombre duplicado. El metodo " + entryMethod.darNombre() + " ya fue declarado.";
                mensajeError = errorTS.construirErrorTS(m, 1);
                throw new errorTS(mensajeError);
            } else {
                //esta redefiniendo el metodo. el metodo era heredado del padre
                this.tablaMetodo.remove(entryMethod.darNombre());
                tienenombredelaclase = nombresIguales(entryMethod, this.darNombre());
                entryMethod.setearEtiqueta("l"+ this.nombre + entryMethod.darNombre());
                noesconstructor = entryMethod.esConstructorDeClase();
                if ((tienenombredelaclase) && !(noesconstructor)) {
                    m = "Intenta declarar un metodo " + entryMethod.darNombre() + " con el mismo nombre de la clase. ";
                    mensajeError = errorTS.construirErrorTS(m, 1);
                    throw new errorTS(mensajeError);
                } else {
                    this.tablaMetodo.put(entryMethod.darNombre(), entryMethod);
                }
                
            }

        }
    }

    /**
     * Verifica que todos los metodos declarados hayan sido definido.
     * Debe haber una correspondencia declaracion/definicion.
     * En esta instancia no chequea concordancia entre parametros, solo consulta 
     * por el atributo definido que sea verdadero indicando que el metodo fue
     * definido.     
     */
    public boolean metodosDefinidos() throws Exception {
        boolean seguir = true;
        boolean todosdedefinidos = true;
        Iterator iter = tablaMetodo.entrySet().iterator();
        Map.Entry e;
        entradaMetodo entryMethod;
        while ((iter.hasNext()) && seguir) {
            e = (Map.Entry) iter.next();
            entryMethod = tablaMetodo.get((String) e.getKey());
            if (!entryMethod.estaMetodoDefinido()) {
                seguir = false;
                todosdedefinidos = false;
                mensajeError = "El metodo " + entryMethod.darNombre() + " no fue definido.";
                throw new errorTS(mensajeError);
            }
        }
        return todosdedefinidos;
    }

    /**
     * Agrega a la tabla de metodos de la clase actual los metodos de sus ancestros
     * en caso de que estos existan. Esto es para mantener la consistencia de las 
     * declaraciones de los metodos.     * 
     */
    public void agregarMetodosAncestros() throws Exception {
        entradaClase ePadre = this.padre;
        LinkedHashMap<String, entradaMetodo> metododsPadre = this.padre.getTablaMetodo();
        entradaMetodo eM;
        for (Map.Entry entry : metododsPadre.entrySet()) {
            eM = ePadre.darMetodo((String) entry.getKey());
            if (!eM.esConstructorDeClase()) {
                this.insertarMetodoTM(eM);
            }
        }
    }

    /**
     * Agrega a la tabla de variables de la clase actual las variables de sus ancestros
     * en caso de que estos existan. Esto es para mantener la consistencia de las 
     * declaraciones de los metodos.     * 
     */
    public void agregarVariablesAncestros() throws Exception {
        entradaClase ePadre = this.padre;
        LinkedHashMap<String, entradaVariable> variablesPadre = padre.getTablaVariable();
        entradaVariable eV;
        for (Map.Entry entry : variablesPadre.entrySet()) {
            eV = ePadre.darVariable((String) entry.getKey());
            this.insertarVariableTV(eV);
        }
    }

    /**
     * Metodo para mostrar el contenido de una entrada metodo.
     */
    public void mostrarMetodo(entradaMetodo m) {

        /*Iterator iter = this.tablaMetodo.entrySet().iterator();
        Map.Entry e;
        entradaClase entryClass;
        
        for (Map.Entry entry : this.tablaMetodo.entrySet()) 
        {
        //System.out.println("Clave : " + entry.getKey()); */
        System.out.print(m.darRetornoMetodo().darNombre());
        System.out.print(" ");
        System.out.print(m.darNombre());
        System.out.print("(...");
        m.mostrarTodosLosParametros();

        System.out.println();
        if (m.estaMetodoDefinido()) {
            System.out.println("Metodo definido");
        } else {
            System.out.println("Metodo no definido");
        }
        if (m.esConstructorDeClase()) {
            System.out.println("Es Constructor de Clase");
        }
           System.out.println("Offset: " + m.darOffsetMethod());

    }
    
    public void setearOffsetMetodo(String nombreMetodo) throws Exception{
        entradaMetodo eM = this.darMetodo(nombreMetodo);
        mayorOffset++;
        eM.setearOffsetMethod(mayorOffset);
    }
    /************************************************************************/
    /*Variables:(Referido al manejo de la tabla de variables) */
    /************************************************************************/
    /**
     * Indica se el nombre del metodo (nameVar) pertenece a la tabla de Variables.
     * @param nameVar es el parametro a consultar para si se encuentra en la tabla
     * de variables.
     * @return boolean true si la variable se encuentra en la tabla de variables. Falso 
     * caso contrario.
     */
    public boolean perteneceTV(String nameVar) {
        if (this.tablaVariable.get(nameVar) != null) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Estrategia: se consulta si el nombre de la variable ya no fue usado por algun mÃ©todo durante 
     * su declaracion. Si no fue usado por algun metodo consulta si ya no hay alguna variable que 
     * tenga ese nombre. Se consulta previamente no no tenga el nombre de la clase actual.
     * inserta un variable en la tabla de variables correspondiente a la clase actual
     * @param entryVar es la variable que se intenta agregar a la tabla de variables.
     * @throws Exception en caso que se intente agregar una variable que ya exista
     * o que contenga el nombre de la clase.
     */
    public void insertarVariableTV(entradaVariable entryVar) throws Exception {
        String m;
        if (entryVar.darNombre().compareTo(this.darNombre()) == 0) {
            m = "Esta usando el nombre de la Clase para la variable: " + entryVar.darNombre();
            mensajeError = errorTS.construirErrorTS(m, lineaerror);
            throw new errorTS(mensajeError);
        } else {
            if (!this.perteneceTV(entryVar.darNombre())) {
                if (!this.perteneceTM(entryVar.darNombre())) {
                    int offset = this.tablaVariable.size();
                    entryVar.setearOffsetLocal(offset + 1);
                    this.tablaVariable.put(entryVar.darNombre(), entryVar);
                } else {
                    //controlar metodos heredados??           
                    m = "Esta usando el nombre de un Metodo de la clase \""+this.darNombre()+"\" para la variable: " + entryVar.darNombre();
                    mensajeError = errorTS.construirErrorTS(m, lineaerror);
                    throw new errorTS(mensajeError);
                }
            } else {
                //controlar las variables heredadas?
                m = "Ya existe una variable definida en la clase con el nombre: " + entryVar.darNombre();
                mensajeError = errorTS.construirErrorTS(m, lineaerror);
                throw new errorTS(mensajeError);
            }
        }
    }

    /**
     * Devuelve una entradaVariable cuando se consulta por el nombre de esa variable.
     * @param key es el nombre de la variable que se esta pidiendo a la tabla de 
     * variables.
     * @return entradaVariable devuelve dado un nombre de una variable una entrada
     * Variable.
     */
    public entradaVariable darVariable(String key) throws Exception {
        String m;
        if (perteneceTV(key)) {
            return this.tablaVariable.get(key);
        } else {
            m = "Esta intentando usar una variable: "+key+" que no fue definida";
            mensajeError = errorTS.construirErrorTS(m, lineaerror);
            throw new errorTS(mensajeError);
        }
    }

    /*********************************************************************/
    /*  Constructor de la clase entradaClase*/
    /*********************************************************************/
    /**
     * Constructor de la clase
     * @param nameClass setea el nombre de la clase.
     */
    public entradaClase(String nameClass) {
        super(nameClass);
        //nombre=nameClass;        
        tieneconstructor = false;
        estipoprimitivo = false;
        constructordefault = false;
        estaDeclarada = false;
        estaDefinida = false;
        padre = null;
       
    }
}